﻿// WaveFileReader.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#define WAVE_FILE_READER_DLLCLASS __declspec(dllexport)

#include "WaveFileReader.h"
#include <fstream>
#include <TChar.h>
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
int CWavFile::m_percent = 0;
CWavFile::CWavFile()
:m_hmmio(NULL),m_dwSize(0),m_errorInfo(0),
m_pbData(NULL),m_channels(1),m_IsHuge(false),m_filename(_T("")),
m_mapoffset(0)
{
}

CWavFile::~CWavFile()
{
    Close();
}
//以指定文件名打开文件
BOOL CWavFile::Open(const _TCHAR* strFileName)
{
    if((m_hmmio = mmioOpen((LPTSTR)strFileName,NULL,MMIO_READ|MMIO_ALLOCBUF))==NULL)
    {
        m_errorInfo = WAVE_FILE_READER_OPEN_FAIL;
        return false;
    }
    m_percent = 0;
    m_filename = strFileName;
    if(!CheckWavData())
    {
        return false;
    }
    SetStart(0);
    //初始化文件头大小
    if(!InitWavHeader())
    {
        m_errorInfo = WAVE_FILE_READER_HEADER_INIT_FAIL;
        return false;
    }
    return true;
}
//读取0通道采样数据到缓存中，读取前应先打开文件
BOOL CWavFile::Read(INT16* pBuf)
{
    if(m_hmmio == NULL)
    {
        m_errorInfo = WAVE_FILE_READER_NOT_OPEN_FILE;
        return false;
    }
    m_percent = 0;
    //获取数据
    SetStart(0);
    m_pbData = GetSourceData(0);
    if(m_pbData==NULL)
    {
        m_errorInfo = WAVE_FILE_READER_GET_SOURCE_DATA_FAIL;
        mmioClose(m_hmmio,0);
        return false;
    }
    if(!GetWavSampleValue(pBuf,0,GetDataSize()))
    {
        FreeHuge();
        return false;
    }
    FreeHuge();
    m_percent = 100;
    return true;
}
//获取最后一步的操作错误代码
int CWavFile::GetLastErrorCode() const
{
    return m_errorInfo;
}
//关闭文件并释放读取的数据
void CWavFile::Close()
{
    if(m_hmmio)
    {
        mmioClose(m_hmmio,0);
        m_hmmio = NULL;
    }
    FreeHuge();
    m_IsHuge = false;
    SetStart(0);
}
//获取全部通道波形数据字节数
DWORD CWavFile::GetDataSize() const
{
    return m_dwSize;
}
//获取波形文件的特定通道的每个采样值,获取数据的多少由datasize决定
//若要获取某个通道的全部数据，可以使用GetDataSize
BOOL CWavFile::GetWavSampleValue(INT16 *pBuf, DWORD channel,DWORD dataSize)
{
    //channel 0 (the left channel)
    if(m_pbData == NULL)
    {
        m_pbData = GetSourceData(dataSize);
    }
    //要获取的原始数据不能超过总数据长度
    if(dataSize>(m_dwSize-m_mapoffset))
    {
        m_errorInfo = WAVE_FILE_READER_DATASIZE_EXCEED;
        return false;
    }
    if(channel >= m_channels)
    {
        m_errorInfo = WAVE_FILE_READER_CHANNEL_ILLEGAL;
        return false;
    }
    DWORD BufSize = GetBufSize(dataSize);
    //先将原来的内存数据清零
    memset(pBuf,0,BufSize);
    //采样值用16位表示，channel为0取左通道数据，否则取右通道数据，每次将2个字节的数据拷贝到缓冲数组中
    DWORD totalSample = BufSize/sizeof(*pBuf);
    double temppercent = m_percent;
    double percentratio = (double)dataSize/m_dwSize*100/totalSample;
    if(m_bitsPerSample==16)
    {
        INT16 *pTemp = (INT16 *)m_pbData;
        pTemp += channel;
        if(m_channels==1)
        {
            memcpy(pBuf,pTemp,BufSize);
        }
        else
        {
            for(int i=0;i<(int)totalSample;i++)
            {
                *(pBuf+i) = *(pTemp+m_channels*i);
                m_percent = temppercent + i*percentratio;
            }
        }
    }
    //采样值用8位表示，取左通道数据，每次将1个字节的数据拷贝到缓冲数组中
    else if(m_bitsPerSample==8)
    {
        unsigned char *pTemp = (unsigned char *)m_pbData;
        pTemp += channel;
        for(int i=0;i<(int)totalSample;i++)
        {
            *(pBuf+i) = *(pTemp+m_channels*i) - 0x80;
            m_percent = temppercent + i*percentratio;
        }
    }
    else
    {
        m_errorInfo = WAVE_FILE_READER_BITPERSAMPLE_ILLEGAL;
        return false;
    }
    return true;
}
//获取波形文件通道数
WORD CWavFile::GetChannels() const
{
    return m_channels;
}
//获取采样位数
WORD CWavFile::GetBitsPerSample() const
{
    return m_bitsPerSample;
}
//获取采样率
DWORD CWavFile::GetSampleRate() const
{
    return m_samplerate;
}
//以文件映射的方式读取特大文件
bool CWavFile::ReadHuge()
{
    if(m_hmmio == NULL)
    {
        m_errorInfo = WAVE_FILE_READER_NOT_OPEN_FILE;
        return false;
    }
    m_percent = 0;
    SetStart(0);
    //每次映射一部分wav文件并将数据读取到dat文件中
    while(m_mapoffset<m_dwSize)
    {
        m_percent = (double)m_mapoffset/m_dwSize*100;
        //每次拷贝的数据字节数
        DWORD copybytes = MAPPINGSIZE;
        DWORD leftbytes = m_dwSize-m_mapoffset;
        //若剩下的字节数不足，则只拷贝剩余字节数
        copybytes = copybytes>leftbytes?leftbytes:copybytes;
        m_pbData = GetSourceData(copybytes);
        if(m_pbData==NULL)
        {
            m_errorInfo = WAVE_FILE_READER_GET_SOURCE_DATA_FAIL;
            mmioClose(m_hmmio,0);
            return false;
        }
        m_IsHuge = true;
        INT16* pdestdata = GetDestMappingData(copybytes);
        if(pdestdata==NULL)
        {
            m_errorInfo = WAVE_FILE_READER_GET_DEST_DATA_FAIL;
            mmioClose(m_hmmio,0);
            return false;
        }
        if(!GetWavSampleValue(pdestdata,0,copybytes))
        {
            m_errorInfo = WAVE_FILE_READER_READ_DATA_FAIL;
            mmioClose(m_hmmio,0);
            return false;
        }
        UnmapViewOfFile(pdestdata);
        FreeHuge();
        //内存映射起点向后移动以便下次映射
        m_mapoffset += copybytes;
    }
    m_percent = 100;
    SetStart(0);

    return true;
}

void CWavFile::FreeHuge()
{
    if(m_pbData!=NULL)
    {
        UnmapViewOfFile(m_pbData);
        m_pbData = NULL;
    }
}
//创建wav文件的内存映射并获取指向数据块的指针
const char* CWavFile::GetSourceData(DWORD mapsize)
{
    HANDLE hfile = CreateFile((LPCTSTR)m_filename.c_str(),GENERIC_READ,
        FILE_SHARE_READ,NULL,OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,NULL);
    if(INVALID_HANDLE_VALUE==hfile)
        return NULL;
    HANDLE hfilemapping = CreateFileMapping(hfile,NULL,PAGE_READONLY,0,0,NULL);
    CloseHandle(hfile);
    if(hfilemapping==NULL)
        return NULL;
    FreeHuge();
    //将偏移圆整成MAPPINGSIZE的整数倍
    DWORD left = (m_mapoffset+m_wavheadsize)%MAPPINGSIZE;
    DWORD offset = (m_mapoffset+m_wavheadsize)/MAPPINGSIZE*MAPPINGSIZE;
    //当映射数据包含文件头时需要增加文件头大小的映射数据
    DWORD bytes = mapsize==0?0:mapsize+left;
    char* temp = (char*)MapViewOfFile(hfilemapping,FILE_MAP_READ,0,offset,bytes);
    CloseHandle(hfilemapping);
    if(temp==NULL)
    {
        DWORD errorcode = GetLastError();
        return NULL;
    }
    temp += left/sizeof(char);
    return temp;
}
//获取接收wav文件中指定字节的数据所需要的缓冲区字节大小
//注意只接受size中的左通道部分
DWORD CWavFile::GetBufSize(DWORD size) const
{
    DWORD bufSize = size;
    bufSize /= GetChannels();
    bufSize *= 16/GetBitsPerSample();
    return bufSize;
}
//获取接收指定原始数据块大小所需的内存映射
INT16* CWavFile::GetDestMappingData(DWORD mapsize)
{
    HANDLE hfile = CreateFile((LPCTSTR)(m_filename+_T(".dat")).c_str(),GENERIC_READ|GENERIC_WRITE,
        FILE_SHARE_READ,NULL,OPEN_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,NULL);
    if(INVALID_HANDLE_VALUE==hfile)
        return NULL;
    HANDLE hfilemapping = CreateFileMapping(hfile,NULL,PAGE_READWRITE,0,GetBufSize(GetDataSize()),NULL);
    CloseHandle(hfile);
    if(hfilemapping==NULL)
        return NULL;
    //将偏移圆整成MAPPINGSIZE的整数倍
    DWORD left = m_mapoffset%MAPPINGSIZE;
    DWORD offset = m_mapoffset/MAPPINGSIZE*MAPPINGSIZE;
    //考虑是否需要添加圆整导致的偏移
    mapsize = mapsize==0?0:mapsize+left;
    //将wav文件映射偏移位置转换成dat文件的映射偏移
    offset = GetBufSize(offset);
    INT16* temp = (INT16*)MapViewOfFile(hfilemapping,
        FILE_MAP_ALL_ACCESS,0,offset,GetBufSize(mapsize));
    CloseHandle(hfilemapping);
    if(temp==NULL)
    {
        DWORD errorcode = GetLastError();
        return NULL;
    }
    //将内存映射指针指向即将读取数据的起始区域
    temp += GetBufSize(left)/sizeof(*temp);
    return temp;
}

bool CWavFile::CheckWavData()
{
    //设置查找的文件类型为wave格式
    m_ckRiff.fccType = mmioFOURCC('W','A','V','E');
    if(mmioDescend(m_hmmio,(LPMMCKINFO)&m_ckRiff,NULL,MMIO_FINDRIFF))
    {
        m_errorInfo = WAVE_FILE_READER_UNRECOGNIZE_WAVE_FILE;
        mmioClose(m_hmmio,0);
        return false;
    }
    //查找fmt信息块
    m_ck.ckid = mmioFOURCC('f','m','t',' ');
    if(mmioDescend(m_hmmio,&m_ck,&m_ckRiff,MMIO_FINDCHUNK))
    {
        m_errorInfo = WAVE_FILE_READER_FIND_FMT_FAIL;
        mmioClose(m_hmmio,0);
        return false;
    }
    PCMWAVEFORMAT pFormat;
    if(mmioRead(m_hmmio,(LPSTR)&pFormat,sizeof(pFormat))==-1)
    {
        m_errorInfo = WAVE_FILE_READER_READ_FORMATINFO_FAIL;
        Close();
        return false;
    }
    if(pFormat.wf.wFormatTag!=WAVE_FORMAT_PCM)
    {
        m_errorInfo = WAVE_FILE_READER_UNSUPPORT_FORMAT;
        Close();
        return false;
    }
    m_channels = pFormat.wf.nChannels;
    m_samplerate = pFormat.wf.nSamplesPerSec;
    if(m_channels>2)
    {
        m_errorInfo = WAVE_FILE_READER_UNSUPPORT_CHANNELS;
        Close();
        return false;
    }
    m_bitsPerSample = pFormat.wBitsPerSample;
    mmioAscend(m_hmmio,&m_ck,0);
    //将读取缓冲指针前进到要查找的data块首部
    m_ck.ckid = mmioFOURCC('d','a','t','a');
    if(mmioDescend(m_hmmio,&m_ck,&m_ckRiff,MMIO_FINDCHUNK))
    {
        m_errorInfo = WAVE_FILE_READER_FIND_DATA_FAIL;
        Close();
        return false;
    }
    //数据字节数
    m_dwSize = m_ck.cksize;
    if(m_dwSize == 0L)
    {
        m_errorInfo = WAVE_FILE_READER_NO_DATA_EXIST;
        Close();
        return false;
    }
    //查找完毕后回归起点
    mmioSeek(m_hmmio,0,SEEK_SET);
    return true;
}
//设置数据读取指针在原始数据块中的偏移字节，传入的位置是不包含文件头的偏移
void CWavFile::SetStart(DWORD offset)
{
    m_mapoffset = offset>m_dwSize?m_dwSize:offset;
}

bool CWavFile::InitWavHeader()
{
    HANDLE hfile = CreateFile((LPCTSTR)m_filename.c_str(),GENERIC_READ,
        FILE_SHARE_READ,NULL,OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,NULL);
    if(INVALID_HANDLE_VALUE==hfile)
        return false;
    HANDLE hfilemapping = CreateFileMapping(hfile,NULL,PAGE_READONLY,0,0,NULL);
    CloseHandle(hfile);
    if(hfilemapping==NULL)
        return false;
    char* temp = (char*)MapViewOfFile(hfilemapping,FILE_MAP_READ,0,0,128);
    CloseHandle(hfilemapping);
    if(temp==NULL)
    {
        DWORD errorcode = GetLastError();
        return false;
    }
    m_wavheadsize = 36;
    temp += m_wavheadsize;
    while(strncmp(temp,"data",4)!=0&&m_wavheadsize<=128)
    {
        temp += 2;
        m_wavheadsize += 2;
    }
    temp += 8;
    if(m_wavheadsize>128)
        return false;
    m_wavheadsize += 8;
    UnmapViewOfFile(temp);
    return true;
}
//将指定大小的数据保存到wav文件中,使用单声道16位原始PCM格式
bool CWavFile::SaveDataToFile(const _TCHAR *filename, const INT16* pdata, DWORD databytes,DWORD samplerate)
{
    struct Wavhead
    {
        char chRIFF[4];
        DWORD dwFileLen;
        char chWAVE[4];
        char chFMT[4];
        DWORD dwFMTLen;
        PCMWAVEFORMAT pwf;
        char chDATA[4];
        DWORD dwDATALen;
    };
    Wavhead header={{'R','I','F','F'},
        0,{'W','A','V','E'},{'f','m','t',' '},
        0,{0},{'d','a','t','a'},0};
    header.dwFileLen = databytes+sizeof(header)-8;
    header.dwFMTLen = sizeof(PCMWAVEFORMAT);
    header.pwf.wf.wFormatTag = WAVE_FORMAT_PCM;
    header.pwf.wf.nChannels = 1;
    header.pwf.wf.nSamplesPerSec = samplerate;
    header.pwf.wf.nAvgBytesPerSec = samplerate*2;
    header.pwf.wf.nBlockAlign = 2;
    header.pwf.wBitsPerSample = 16;
    header.dwDATALen = databytes;
    HANDLE hfile = CreateFile((LPCTSTR)filename,GENERIC_READ|GENERIC_WRITE,
        FILE_SHARE_READ,NULL,CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,NULL);
    if(INVALID_HANDLE_VALUE==hfile)
        return false;
    bool flag=true;
    DWORD bytes;
    if(!WriteFile(hfile,&header,sizeof(header),&bytes,NULL))
        flag = false;
    if(!WriteFile(hfile,pdata,databytes,&bytes,NULL))
        flag = false;
    CloseHandle(hfile);
    return flag;
}
